package rpc_protobuf

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"net/rpc"
	"net/rpc/jsonrpc"
)

const (
	// 这个是RPC服务抽象的包路径，并非完全等价Go语言的包路径
	HelloServiceName = "path/to/pkg.HelloService"
)

// 我们将RPC服务的接口规范分为三个部分：首先是服务的名字，然后是服务要实现的详细方法列表，最后是注册该类型服务的函数
type HelloServiceInterface interface {
	Hello(request string, reply *string) error
}

type HelloService struct {
	conn    net.Conn //对应链接的conn成员;支持上下文
	isLogin bool
}

// 必须满足Go语言的RPC规则：方法只能有两个可序列化的参数，其中第二个参数是指针类型，并且返回一个error类型，同时必须是公开的方法
func (s *HelloService) Hello(request string, reply *string) error {
	if !s.isLogin { // 可以验证是否登录了
		return fmt.Errorf("please login")
	}
	*reply = "hello " + request + ", from" + s.conn.RemoteAddr().String() //可以根据conn成员识别不同链接的RPC调用
	return nil
}

// 可以方便地为RPC服务增加简单的登陆状态的验证
func (p *HelloService) Login(request string, reply *string) error {
	if request != "user:password" {
		return fmt.Errorf("auth failed")
	}
	log.Println("login ok")
	p.isLogin = true
	return nil
}

// 集合proto生成代码使用
//func (s *HelloService) Hello(request *String, reply *string) error {
//	*reply = "hello " + request.GetValue()
//	return nil
//}

// 标准库的RPC默认采用Go语言特有的gob编码
// Go语言的RPC框架有两个比较有特色的设计：
//	一个是RPC数据打包时可以通过插件实现自定义的编码和解码；
//	另一个是RPC建立在抽象的io.ReadWriteCloser接口之上的，
// 我们可以将RPC架设在不同的通讯协议之上。这里我们将尝试通过官方自带的net/rpc/jsonrpc扩展实现一个跨语言的RPC。见下面
func RpcServerDemo() {
	err := RegisterHelloService(new(HelloService))
	if err != nil {
		panic(err)
	}

	listener, err := net.Listen("tcp", ":1234")
	if err != nil {
		panic(err)
	}

	for {
		connect, err := listener.Accept()
		if err != nil {
			panic(err)
		}

		go rpc.ServeConn(connect)
	}
}

func RegisterHelloService(svc HelloServiceInterface) error {
	return rpc.RegisterName(HelloServiceName, svc)
}

// ##############   下面是Client  ###################

// 为了简化客户端用户调用RPC函数，我们在可以在接口规范部分增加对客户端的简单包装l
// 而且提高了代码的安全性
// 客户端用户不用再担心RPC方法名字或参数类型不匹配等低级错误的发生
type HelloServiceClient struct {
	*rpc.Client
}

// 用法含义
// 1. 创建一个HelloServiceInterface地址，但不会分配内存的,并且如果给字段赋值会报错。
// 2. 在代码中判断HelloServiceClient这个struct是否实现了HelloServiceInterface这个interface。
var _ HelloServiceInterface = (*HelloServiceClient)(nil)

func DialHelloService(network, address string) (*HelloServiceClient, error) {
	client, err := rpc.Dial(network, address)
	if err != nil {
		panic(err)
	}

	return &HelloServiceClient{Client: client}, nil
}

func (c *HelloServiceClient) Hello(request string, reply *string) error {
	return c.Call(HelloServiceName+".Hello", request, reply)
}

func RpcClientDemo() {
	client, err := DialHelloService("tcp", "123.56.5.170:8443")
	if err != nil {
		panic(err)
	}

	var reply string
	err = client.Hello("hello", &reply)
	if err != nil {
		panic(err)
	}

	fmt.Println(reply)
}

// ################# 这里我们将尝试通过官方自带的net/rpc/jsonrpc扩展实现一个跨语言的RPC。 ##################

func JsonRpcServerDemo() {
	err := RegisterHelloService(new(HelloService))
	if err != nil {
		panic(err)
	}

	listener, err := net.Listen("tcp", ":1234")
	if err != nil {
		panic(err)
	}

	for {
		connect, err := listener.Accept()
		if err != nil {
			panic(err)
		}

		// 这个地方是变化点
		// 如果是ServeConn方法，会默认传入gob编解码
		// ServeCodec +  NewServerCodec, 会传入json编解码
		go rpc.ServeCodec(jsonrpc.NewServerCodec(connect))
	}
}

func JsonRpcClientDemo() {
	conn, err := net.Dial("tcp", "localhost:1234")
	if err != nil {
		panic(err)
	}

	client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))

	// 这是发送的json请求;nc -lp 1234(window的命令)
	// req: {"method":"path/to/pkg.HelloService.Hello","params":["Hello"],"id":0}
	// 命令: echo '{"method":"path/to/pkg.HelloService.Hello","params":["Hello"],"id":0}'|nc localhost 1234
	// resp: {"id":0,"result":"hello Hello","error":null}
	var reply string
	err = client.Call(HelloServiceName+".Hello", "Hello", &reply)
	if err != nil {
		panic(err)
	}

	fmt.Println(reply)
}

// #####################   Http上的RPC  #####################################

// Go语言内在的RPC框架已经支持在Http协议上提供RPC服务
func HttpRpcServerDemo() {
	err := RegisterHelloService(new(HelloService))
	if err != nil {
		panic(err)
	}

	http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
		var conn io.ReadWriteCloser = struct {
			io.Writer
			io.ReadCloser
		}{
			ReadCloser: r.Body,
			Writer:     w,
		}

		err = rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
		if err != nil {
			panic(err)
		}
	})

	err = http.ListenAndServe(":1234", nil)
	if err != nil {
		panic(err)
	}
}
